package org.moonlight.jvm.natives.java.lang.throwable;

import org.moonlight.jvm.natives.AbstractNativeMethod;
import org.moonlight.jvm.rtda.exclusive.Frame;
import org.moonlight.jvm.rtda.exclusive.Thread;
import org.moonlight.jvm.rtda.share.Object;
import org.moonlight.jvm.rtda.share.heap.Class;
import org.moonlight.jvm.rtda.share.heap.classmember.Method;

/**
 * java.lang.Throwable   private native Throwable fillInStackTrace(int dummy);
 *
 * @author Moonlight
 * @createTime 2023/11/9 15:48
 **/
public class FillInStackTrace extends AbstractNativeMethod {

    public FillInStackTrace() {
        super("java/lang/Throwable", "fillInStackTrace", "(I)Ljava/lang/Throwable;");
    }

    @Override
    public void nativeMethod(Frame frame) {
        Object that = frame.getLocalVars().getThis();
        frame.getOperandStack().pushRef(that);

        Throwable[] stackTraceElements = createStackTraceElements(that, frame.getThread());
        that.setExtra(stackTraceElements);
    }

    public Throwable[] createStackTraceElements(Object obj, Thread thread) {
        // 由于栈顶两帧正在执行fillInStackTrace(int)和fillInStackTrace()方法，所以需要跳过这两帧。
        // 这两帧下面的几帧正在执行异常类的构造函数，所以也要跳过，具体要跳过多少帧数则要看异常类的继承层次。
        int skip = distanceToObject(obj.getClazz()) + 2;

        Frame[] frames = thread.getFrames();
        Throwable[] throwableArr = new Throwable[frames.length - skip];
        int idx = 0;
        for (int i = skip; i < frames.length; i++) {
            throwableArr[idx] = createStackTraceElement(frames[i]);
        }
        return throwableArr;
    }

    public Throwable createStackTraceElement(Frame frame) {
        Method method = frame.getMethod();
        Class clazz = method.getClazz();

        Throwable.StackTraceElement se = new Throwable.StackTraceElement();
        se.setFileName(clazz.getSourceFile());
        se.setClassName(clazz.getJavaName());
        se.setMethodName(method.getName());
        se.setLineNumber(method.getLineNumber(frame.getNextPc() - 1));

        Throwable throwable = new Throwable();
        throwable.setStackTraceElement(se);

        return throwable;
    }

    public int distanceToObject(Class clazz) {
        int dis = 0;
        for (Class c = clazz.getSuperClass(); c != null; c = c.getSuperClass()) {
            dis++;
        }
        return dis;
    }

}
